001 package net.sf.xdc.processing;
002
003 /*
004 * Copyright 2005-2006 Jens Voß.
005 *
006 * Licensed under the GNU Lesser General Public License (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://opensource.org/licenses/lgpl-license.php
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 import java.io.File;
020 import java.io.FileNotFoundException;
021 import java.io.FileReader;
022 import java.io.IOException;
023 import java.io.Reader;
024 import java.io.StringWriter;
025 import java.util.Arrays;
026 import java.util.HashSet;
027 import java.util.Iterator;
028 import java.util.Set;
029 import java.util.SortedMap;
030 import java.util.SortedSet;
031 import java.util.StringTokenizer;
032 import java.util.TreeMap;
033 import java.util.TreeSet;
034
035 import net.sf.xdc.util.IOUtils;
036 import net.sf.xdc.util.Logging;
037 import net.sf.xdc.util.PathDescriptor;
038 import org.apache.commons.cli.CommandLine;
039 import org.apache.log4j.Logger;
040
041 /**
042 * The <code>XdcSourceCollector</code> class is used to assemble the possible
043 * XML source files specified for processing into XDC documentation.
044 *
045 * @author Jens Voß
046 * @since 0.5
047 * @version 0.5
048 */
049 public class XdcSourceCollector {
050
051 private static final Logger LOG = Logging.getLogger();
052
053 private static final String[] EMPTY_STRING_ARRAY = new String[0];
054
055 private CommandLine commandLine;
056 private String[] sourcePaths;
057 private File[] sourceDirs;
058 private String[] subpackages;
059 private String[] excluded;
060 private String[] filePaths;
061 private boolean defaultExcludes;
062 private SortedSet xdcSources = new TreeSet(); // SortedSet<XdcSource>
063 private SortedMap xdcPackages = new TreeMap(); // SortedMap<String, XdcPackage>
064
065 private FileAction addAction = new FileAction() {
066 public void execute(XdcSource xdcSource) {
067 XdcSourceCollector.this.xdcSources.add(xdcSource);
068 String packageName = xdcSource.getPackageName();
069 XdcPackage xdcPackage = (XdcPackage) xdcPackages.get(packageName);
070 if (xdcPackage == null) {
071 xdcPackage = new XdcPackage(packageName);
072 xdcPackages.put(packageName, xdcPackage);
073 }
074 xdcPackage.addSource(xdcSource);
075 }
076 };
077
078 private FileAction removeAction = new FileAction() {
079 public void execute(XdcSource xdcSource) {
080 XdcSourceCollector.this.xdcSources.remove(xdcSource);
081 String packageName = xdcSource.getPackageName();
082 XdcPackage xdcPackage = (XdcPackage) xdcPackages.get(packageName);
083 xdcPackage.removeSource(xdcSource);
084 }
085 };
086
087 /**
088 * Public constructor.
089 * @param commandLine The <code>CommandLine</code> containing all option
090 * values of the XDC invocation
091 */
092 public XdcSourceCollector(CommandLine commandLine) {
093 this.commandLine = commandLine;
094 this.sourcePaths = getStringArrayFromOption("sourcepath", false,
095 new String[] {"."}, ";");
096 this.subpackages = getStringArrayFromOption("subpackages", true,
097 EMPTY_STRING_ARRAY, ":");
098 this.excluded = getStringArrayFromOption("exclude", true,
099 EMPTY_STRING_ARRAY, ":");
100 this.filePaths = commandLine.getArgs();
101 for (int i = 0; i < filePaths.length; i++) {
102 filePaths[i] = filePaths[i].replace('\\', '/');
103 }
104 defaultExcludes = commandLine.hasOption("defaultexcludes");
105 collectSourceDirs();
106 collectSubpackages();
107 collectArgumentFiles();
108 }
109
110 /**
111 * This getter method retrieves all assembled XML source files.
112 *
113 * @return All XML source files specified for processing (in the form of an
114 * array of <code>XdcSource</code> objects)
115 */
116 public XdcSource[] getXdcSources() {
117 return (XdcSource[]) xdcSources.toArray(new XdcSource[xdcSources.size()]);
118 }
119
120 /**
121 * This method retrieves an {@link XdcPackage} object specified for processing
122 * by the XDC tool.
123 *
124 * @param packageName The name of the package
125 * @return The <code>XdcPackage</code> with the specified name
126 */
127 public XdcPackage getXdcPackage(String packageName) {
128 return (XdcPackage) xdcPackages.get(packageName);
129 }
130
131 /**
132 * This method retrieves all <code>XdcSource</code> objects contained in an
133 * <code>XdcPackage</code>.
134 *
135 * @param packageName The name of the package from which the sources are
136 * returned
137 * @return All XML source files (in the form of an array of
138 * <code>XdcSource</code> objects) contained in the package with the
139 * specified name.
140 */
141 public XdcSource[] getXdcSources(String packageName) {
142 XdcPackage xdcPackage = (XdcPackage) xdcPackages.get(packageName);
143 return xdcPackage.getXdcSources();
144 }
145
146 /**
147 * This method retrieves a particular <code>XdcSource</code> by its
148 * (fully-qualified) source name.
149 *
150 * @param sourceName The fully-qualified name of the <code>XdcSource</code>
151 * to be retrieved
152 * @return The <code>XdcSource</code> with the given name
153 */
154 public XdcSource getXdcSource(String sourceName) {
155 int pos = sourceName.lastIndexOf('/');
156 String packageName = pos >= 0 ? sourceName.substring(0, pos) : "";
157 XdcPackage xdcPackage = (XdcPackage) xdcPackages.get(packageName);
158 return xdcPackage != null ? xdcPackage.getXdcSource(sourceName.substring(pos + 1)) : null;
159 }
160
161 private String[] getStringArrayFromOption(String option, boolean replacePeriod,
162 String[] defaultValue, String delim) {
163 if (commandLine.hasOption(option)) {
164 String spVal = commandLine.getOptionValue(option);
165 StringTokenizer tok = new StringTokenizer(spVal, delim, false);
166 Set sps = new HashSet();
167 while (tok.hasMoreTokens()) {
168 String nextElem = tok.nextToken().replace('\\', '/');
169 if (replacePeriod) {
170 nextElem = nextElem.replace('.', '/');
171 }
172 sps.add(nextElem);
173 }
174 return (String[]) sps.toArray(new String[sps.size()]);
175 }
176 else {
177 return defaultValue;
178 }
179 }
180
181 private void collectSourceDirs() {
182 Set dirs = new HashSet(sourcePaths.length);
183 for (Iterator iter = Arrays.asList(sourcePaths).iterator(); iter.hasNext();) {
184 String path = (String) iter.next();
185 File dir;
186 try {
187 dir = new File(path).getCanonicalFile();
188 }
189 catch (IOException e) {
190 LOG.error(e.getMessage(), e);
191 continue;
192 }
193 if (!dir.exists()) {
194 LOG.warn("Source path " + path + " does not exist.");
195 }
196 else if (!dir.isDirectory()) {
197 LOG.warn("Source path " + path + " is not a directory.");
198 }
199 else {
200 dirs.add(dir);
201 }
202 }
203 this.sourceDirs = (File[]) dirs.toArray(new File[dirs.size()]);
204 }
205
206 private void collectSubpackages() {
207 Set extensions = new HashSet();
208 if (commandLine.hasOption("extensions")) {
209 StringTokenizer tok = new StringTokenizer(commandLine.getOptionValue("extensions"), ",", false);
210 while (tok.hasMoreTokens()) {
211 extensions.add(tok.nextToken());
212 }
213 }
214 // add files specified by the "-subpackages" option ...
215 for (int i = 0; i < sourceDirs.length; i++) {
216 File sourceDir = sourceDirs[i];
217 for (int j = 0; j < subpackages.length; j++) {
218 String subpackage = subpackages[j];
219 File pkg = new File(sourceDir, subpackage);
220 if (pkg.exists() && pkg.isDirectory()) {
221 recurse(new FileSelector(pkg, extensions, defaultExcludes), subpackage, addAction, sourceDir);
222 }
223 }
224 }
225 // ... and then remove thos specified by the "-exclude" option
226 for (int i = 0; i < sourceDirs.length; i++) {
227 File sourceDir = sourceDirs[i];
228 for (int j = 0; j < excluded.length; j++) {
229 String excludedPkg = excluded[j];
230 File pkg = new File(sourceDir, excludedPkg);
231 if (pkg.exists() && pkg.isDirectory()) {
232 recurse(new FileSelector(pkg, extensions, commandLine.hasOption("defaultexcludes")), excludedPkg, removeAction, sourceDir);
233 }
234 }
235 }
236 }
237
238 private void collectArgumentFiles() {
239 PathDescriptor[] descriptors = new PathDescriptor[filePaths.length];
240 for (int i = 0; i < filePaths.length; i++) {
241 String filePath = filePaths[i];
242 descriptors[i] = new PathDescriptor(filePath, defaultExcludes);
243 }
244 for (int i = 0; i < sourceDirs.length; i++) {
245 File sourceDir = sourceDirs[i];
246 FileSelector selector = new PatternSelector(sourceDir, descriptors);
247 recurse(selector, "", addAction, sourceDir);
248 }
249 }
250
251 private void recurse(FileSelector selector, String packageName,
252 FileAction fileAction,
253 File sourceDir) {
254 File[] files = selector.selectFiles();
255 for (int i = 0; i < files.length; i++) {
256 File file = files[i];
257 if (file.isFile()) {
258 DialectHandler handler = DialectHandler.getDialectHandler(file, packageName, commandLine);
259 fileAction.execute(new XdcSource(file, sourceDir, packageName, handler));
260 }
261 else if (file.isDirectory()) {
262 String filename = file.getName();
263 // jv, 05-Jul-2006: bug #1517436
264 String subPackageName = packageName != null && packageName.length() > 0 ? packageName + '/' + filename : filename;
265 recurse(selector.moveToSubdir(filename), subPackageName, fileAction,
266 sourceDir);
267 }
268 }
269 }
270
271 /**
272 * This method returns the directory specified as the first argument of the
273 * <code>-sourcepath</code> option. (This value is used as the root directory
274 * of the XDC output if no <code>-d</code> option is specified.)
275 *
276 * @return The <code>File</code> specified as the first sourcepath
277 */
278 public File getLeadingSourcePath() {
279 return this.sourceDirs[0];
280 }
281
282 /**
283 * This method returns an array of the names of all packages containing
284 * XML source files specified for processing.
285 *
286 * @return The names of all collected <code>XdcPackages</code>
287 */
288 public String[] getPackageNames() {
289 Set retVal = xdcPackages.keySet();
290 return (String[]) retVal.toArray(new String[retVal.size()]);
291 }
292
293 /**
294 * This method is used to determine the number of frames contained in the
295 * main frameset of the generated XDC documentation.
296 *
297 * @return If sources from just one <code>XdcPackage</code> have been
298 * assembled, the method returns 2; otherwise it returns 3.
299 */
300 public int getFramesetSize() {
301 if (xdcPackages.size() == 1) {
302 return 2;
303 }
304 else {
305 return 3;
306 }
307 }
308
309 /**
310 * This method attempts to extract text from an overview file (which is
311 * specified by the <code>-overview</code> option and must be placed in of the
312 * root directories specified as arguments to the <code>-sourcepath</code>
313 * options).
314 *
315 * @return All text placed between the opening and the closing <body>
316 * tags of the overview file. Note that this text is returned "raw",
317 * i.e. no XDC tags are processed yet.
318 */
319 public String getSummaryText() {
320 if (!commandLine.hasOption("overview")) {
321 return "";
322 }
323 String overview = commandLine.getOptionValue("overview");
324 for (int i = 0; i < sourceDirs.length; i++) {
325 File packageFile = new File(sourceDirs[i], overview);
326 if (packageFile.exists()) {
327 String retVal = getSummaryText(packageFile);
328 if (retVal != null) {
329 return retVal;
330 }
331 }
332 }
333 File packageFile = new File(overview);
334 if (packageFile.exists()) {
335 String retVal = getSummaryText(packageFile);
336 if (retVal != null) {
337 return retVal;
338 }
339 }
340 return "";
341 }
342
343 private static String getSummaryText(File packageFile) {
344 String retVal = null;
345 Reader in = null;
346 try {
347 in = new FileReader(packageFile);
348 StringWriter out = new StringWriter();
349 IOUtils.copy(in, out);
350 StringBuffer buf = out.getBuffer();
351 int pos1 = buf.indexOf("<body>");
352 int pos2 = buf.lastIndexOf("</body>");
353 if (pos1 >= 0 && pos1 < pos2) {
354 retVal = buf.substring(pos1 + 6, pos2);
355 }
356 else {
357 retVal = "";
358 }
359 }
360 catch (FileNotFoundException e) {
361 LOG.error(e.getMessage(), e);
362 }
363 catch (IOException e) {
364 LOG.error(e.getMessage(), e);
365 }
366 finally {
367 if (in != null) {
368 try {
369 in.close();
370 }
371 catch (IOException e) {
372 LOG.error(e.getMessage(), e);
373 }
374 }
375 }
376 return retVal;
377 }
378
379 }